package joc.internal.transformer;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;

import javassist.CtBehavior;
import javassist.CtClass;
import joc.customize.Contract;
import joc.customize.ForBehaviorCallAtBeginningOfBehavior;
import joc.customize.ForBehaviorEvaluateAtBeginningOfBehavior;
import joc.customize.ForBehaviorEvaluateAtEndOfBehavior;
import joc.customize.ForMethodEvaluateAtEndOfMethodWithReturnValue;
import joc.internal.compiler.CastExp;
import joc.internal.compiler.NestedExp;

public class GenericBehaviorTransformer extends DeclaredBehaviorTransformer {
	public GenericBehaviorTransformer(Transformer transformer) {
		super(transformer);
	}

	@Override
	public void transform(CtClass clazz, CtBehavior behavior) throws Exception {
		getLogger().info("behavior " + behavior.getLongName());
		for (Class<? extends Annotation> contractAnnotation : Transformer.getContractMap().keySet()) {
			Annotation annotation = (Annotation) behavior.getAnnotation(contractAnnotation);
			if (annotation != null) {
				getLogger().info(
						"behavior " + behavior.getLongName() + " has contract "
								+ Transformer.getContractMap().get(contractAnnotation).getClass().getName());
				applyContract(behavior, Transformer.getContractMap().get(contractAnnotation), contractAnnotation,
						annotation);
			}
		}
	}

	private void applyContract(CtBehavior behavior, Class<? extends Contract<?>> contract,
			Class<? extends Annotation> annotationClass, Annotation annotation) throws Exception {
		for (Method contractMethod : getContractMethodsWithAnnotation(contract,
				ForBehaviorEvaluateAtBeginningOfBehavior.class)) {
			getContractCallString(true, behavior, contractMethod, contract, annotationClass, annotation, true)
					.insertBefore(behavior);
			getLogger().info(
					"inserted " + contract.getClass().getName() + " with " + contractMethod.getName() + " in behavior "
							+ behavior.getLongName());
		}
		for (Method contractMethod : getContractMethodsWithAnnotation(contract,
				ForBehaviorCallAtBeginningOfBehavior.class)) {
			getContractCallString(true, behavior, contractMethod, contract, annotationClass, annotation, false)
					.insertBefore(behavior);
			getLogger().info(
					"inserted " + contract.getClass().getName() + " with " + contractMethod.getName() + " in behavior "
							+ behavior.getLongName());
		}
		for (Method contractMethod : getContractMethodsWithAnnotation(contract,
				ForBehaviorEvaluateAtEndOfBehavior.class)) {
			getContractCallString(false, behavior, contractMethod, contract, annotationClass, annotation, true)
					.insertAfter(behavior);
			getLogger().info(
					"inserted " + contract.getClass().getName() + " with " + contractMethod.getName() + " in behavior "
							+ behavior.getLongName());
		}
		for (Method contractMethod : getContractMethodsWithAnnotation(contract,
				ForMethodEvaluateAtEndOfMethodWithReturnValue.class)) {
			NestedExp returnValue = handlePrimitiveExp(contractMethod.getParameterTypes()[1], NestedExp.RETURN_VALUE);
			getContractCallString(false, behavior, contractMethod, contract, annotationClass, annotation, true,
					new CastExp(contractMethod.getParameterTypes()[1], returnValue)).insertAfter(behavior);
			getLogger().info(
					"inserted " + contract.getClass().getName() + " with " + contractMethod.getName() + " in behavior "
							+ behavior.getLongName());
		}
	}

}
